package com.childenglish.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.UUID;

/**
 * CSRF防护过滤器
 * 生成和验证CSRF Token
 */
public class CSRFFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(CSRFFilter.class);
    private static final String CSRF_TOKEN_NAME = "X-CSRF-TOKEN";
    private static final String CSRF_TOKEN_HEADER = "X-CSRF-TOKEN";

    // 不需要CSRF验证的路径
    private static final String[] EXCLUDED_PATHS = {
        "/health",
        "/login",
        // "/api/register", // 已移除，用于测试CSRF防护
        "/api/check-username",
        "/api/validate-password",
        "/api/validate-email",
        "/api/validate-phone",
        "/api/classes",
        "/api/captcha", // 验证码获取端点，GET请求不需要验证
        "/api/csrf-token", // CSRF Token获取端点，不需要验证
        "/csrf-token", // CSRF Token获取端点（备选），不需要验证
        "/api/encryption/info", // 加密信息查询，GET请求不需要验证
        "/file/", // 所有文件相关API都不需要CSRF验证（包括上传、下载、列表等）
        "/api/guidance/upload-audio", // 指导语音上传，文件上传不需要CSRF验证
        "/api/guidance/upload-courseware" // 指导课件上传，文件上传不需要CSRF验证
    };

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String requestURI = httpRequest.getRequestURI();
        boolean isExcluded = isExcludedPath(requestURI);

        // GET请求：生成或返回CSRF Token（所有GET请求都应该生成Token，包括排除列表中的）
        if ("GET".equalsIgnoreCase(httpRequest.getMethod())) {
            HttpSession session = httpRequest.getSession(true);
            String csrfToken = (String) session.getAttribute(CSRF_TOKEN_NAME);
            
            if (csrfToken == null) {
                csrfToken = UUID.randomUUID().toString();
                session.setAttribute(CSRF_TOKEN_NAME, csrfToken);
                logger.debug("生成新的CSRF Token: {} for path: {}", csrfToken, requestURI);
            } else {
                logger.debug("使用已存在的CSRF Token: {} for path: {}", csrfToken, requestURI);
            }
            
            // 将Token添加到响应头（必须在调用chain.doFilter之前设置）
            httpResponse.setHeader(CSRF_TOKEN_HEADER, csrfToken);
            // 确保CORS允许暴露此响应头
            httpResponse.setHeader("Access-Control-Expose-Headers", "X-CSRF-TOKEN");
            
            logger.debug("已设置CSRF Token到响应头: {} for path: {}", csrfToken, requestURI);
            
            chain.doFilter(request, response);
            return;
        }

        // POST/PUT/DELETE请求：验证CSRF Token（排除列表中的路径跳过验证）
        if (isModifyingMethod(httpRequest.getMethod())) {
            // 如果在排除列表中，跳过CSRF验证
            if (isExcluded) {
                logger.debug("路径 {} 在排除列表中，跳过CSRF验证", requestURI);
                chain.doFilter(request, response);
                return;
            }

            HttpSession session = httpRequest.getSession(false);
            if (session == null) {
                // 会话不存在，可能是后端重启导致的，尝试创建新会话并生成Token
                logger.debug("会话不存在，创建新会话并生成CSRF Token: {}", requestURI);
                session = httpRequest.getSession(true);
                String newCsrfToken = UUID.randomUUID().toString();
                session.setAttribute(CSRF_TOKEN_NAME, newCsrfToken);
                // 将新Token添加到响应头，让前端可以获取
                httpResponse.setHeader(CSRF_TOKEN_HEADER, newCsrfToken);
                httpResponse.setHeader("Access-Control-Expose-Headers", "X-CSRF-TOKEN");
                // 对于某些初始化请求，允许继续处理（但记录警告）
                // 对于其他请求，返回错误提示前端重新获取Token
                if (requestURI.contains("/api/csrf-token") || requestURI.contains("/csrf-token")) {
                    // CSRF Token获取请求，允许继续
                    chain.doFilter(request, response);
                    return;
                } else {
                    // 其他请求，提示前端会话已失效，需要重新获取Token
                    logger.warn("CSRF验证失败: 会话不存在，路径: {}", requestURI);
                    httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    httpResponse.setContentType("application/json;charset=UTF-8");
                    httpResponse.getWriter().write("{\"code\":403,\"message\":\"会话已失效，请刷新页面重新获取CSRF Token\"}");
                    return;
                }
            }

            String sessionToken = (String) session.getAttribute(CSRF_TOKEN_NAME);
            String requestToken = httpRequest.getHeader(CSRF_TOKEN_HEADER);
            
            // 如果Header中没有，尝试从参数中获取
            if (requestToken == null || requestToken.isEmpty()) {
                requestToken = httpRequest.getParameter(CSRF_TOKEN_NAME);
            }
            
            // 对于multipart/form-data请求，也可能从表单数据中获取
            if ((requestToken == null || requestToken.isEmpty()) && 
                httpRequest.getContentType() != null && 
                httpRequest.getContentType().startsWith("multipart/form-data")) {
                // multipart请求的参数会在doFilter之后才能读取，这里先记录日志
                logger.debug("multipart请求，尝试从表单数据获取Token");
            }

            if (sessionToken == null || !sessionToken.equals(requestToken)) {
                logger.warn("CSRF验证失败: Token不匹配. Session: {}, Request: {}, Path: {}", 
                    sessionToken, requestToken, requestURI);
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                httpResponse.setContentType("application/json;charset=UTF-8");
                httpResponse.getWriter().write("{\"code\":403,\"message\":\"CSRF验证失败\"}");
                return;
            }

            logger.debug("CSRF验证通过: {} for path: {}", requestToken, requestURI);
        }

        chain.doFilter(request, response);
    }

    /**
     * 检查是否为修改性HTTP方法
     */
    private boolean isModifyingMethod(String method) {
        return "POST".equalsIgnoreCase(method) ||
               "PUT".equalsIgnoreCase(method) ||
               "DELETE".equalsIgnoreCase(method) ||
               "PATCH".equalsIgnoreCase(method);
    }

    /**
     * 检查路径是否在排除列表中
     */
    private boolean isExcludedPath(String path) {
        for (String excludedPath : EXCLUDED_PATHS) {
            if (path.startsWith(excludedPath)) {
                return true;
            }
        }
        return false;
    }
}

